package com.hexacta.hrs.domain.staffing.relationship;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;

import com.hexacta.hrs.domain.staffing.Particionador;

/**
 * Se encarga de fusionar aquellos relationships que son similares.
 * @author ncortinez
 * 
 */
public class RelationshipEnsamblador {

	/**
	 * Uno todos aquellos objetos del tipo RequirementAssignmentRelationship que sean similares.
	 * 
	 * @param relationships: lista de relationships a unir.
	 */
	public static void unirRelationships(List<RequirementAssignmentRelationship> relationships) {
		
		unirRelationshipsPorPorcentaje(relationships);
		
		unirRelationshipsCorrelativos(relationships);
	}

	/**
	 * Uno todos aquellos objetos del tipo RequirementAssignmentRelationship que sean similares y cuyo rango de fechas sea correlativo.
	 * Por objetos similares nos referimos a aquellos que son identicos a excepcion del rango de fechas. 
	 * 
	 * Ejemplo: si la lista contiene dos objetos cuyos rangos de fechas son [01/01/2010,10/01/2010] y [11/01/2010,20/01/2010] respectivamente,
	 * el metodo coloca en la lista un nuevo objeto con el rango horario [01/01/2010,20/01/2010], y elimina los otros dos.
	 * 
	 * @param relationships: lista de relationships a unir.
	 */
	public static void unirRelationshipsCorrelativos(
			List<RequirementAssignmentRelationship> relationships) {
		
		boolean volverARecorrer = false;
		
		for(int i=0; (!volverARecorrer)&&(i< relationships.size()); i++){
			RequirementAssignmentRelationship asignacion = relationships.get(i);

			// Busco una parecida, en la que la fecha de fin coincida con mi fecha de inicio.
			RequirementAssignmentRelationship correlativa = (RequirementAssignmentRelationship) CollectionUtils.find(relationships, new CorrelativaPorIzquierda(asignacion));;
			if(correlativa!=null){
				//La agrego a la lista de asignaciones a revisar y elimino las dos originales.
				relationships.remove(asignacion);
				relationships.remove(correlativa);
				correlativa.setEndDate((Calendar) asignacion.getEndDate().clone());
				relationships.add(correlativa);
				
				//Vuelvo a recorrer la lista desde cero.
				volverARecorrer = true;
			}
			
			// Busco una parecida, en la que la fecha de inicio coincida con mi fecha de fin.
			correlativa = null;
			correlativa = (RequirementAssignmentRelationship) CollectionUtils.find(relationships, new CorrelativaPorDerecha(asignacion));
			if(correlativa!=null){
				//La agrego a la lista de asignaciones a revisar y elimino las dos originales.
				relationships.remove(asignacion);
				relationships.remove(correlativa);
				
				asignacion.setEndDate((Calendar) correlativa.getEndDate().clone());
				relationships.add(asignacion);
				
				//Vuelvo a recorrer la lista desde cero.
				volverARecorrer = true;
			}
		}
		
		if(volverARecorrer) unirRelationshipsCorrelativos(relationships);
	}
	
	/**
	 * Uno todos aquellos objetos del tipo RequirementAssignmentRelationship que sean identicos, a excepcion de su porcentaje de asignacion (assignmentPorcentage)
	 * En este caso, los dos objetos se unen en uno solo, el cual contiene los porcentajes sumados.
	 * 
	 * @param relationships: lista de relationships a unir.
	 */
	public static void unirRelationshipsPorPorcentaje(List<RequirementAssignmentRelationship> relationships) {
		
		boolean volverARecorrer = false;
		
		// Busco aquellas relaciones del mismo empleado que se superponen en fechas, y uno sus porcentajes.
		for(int i=0; (!volverARecorrer)&&(i< relationships.size()); i++){
			RequirementAssignmentRelationship relationship = relationships.get(i);
			
			List<RequirementAssignmentRelationship> restoDeLosElementos = new ArrayList<RequirementAssignmentRelationship>(relationships);
			restoDeLosElementos.remove(relationship);
			RequirementAssignmentRelationship incluida = (RequirementAssignmentRelationship) CollectionUtils.find(restoDeLosElementos, new RelationshipIncluidaEnOtra(relationship));
			
			// Busco aquellas relaciones del mismo empleado que se superponen en fechas,
			if(incluida != null){
				// Si las fechas de inicio y fin no coinciden exactamente, hay que particionarlo y volver a recorrer la lista.
				if(!incluida.getStartDate().equals(relationship.getStartDate()) || !incluida.getEndDate().equals(relationship.getEndDate())){
					relationships.addAll(Particionador.particionar(incluida, relationship));
				}
				else
				{
					// Como las fechas coinciden exactamente uno los porcentajes de asignaciones en uno solo.
					try {
						relationships.remove(relationship);
						relationships.remove(incluida);
						RequirementAssignmentRelationship newRelation = (RequirementAssignmentRelationship) relationship.clone();
						
						newRelation.setPorcentage(relationship.getPorcentage() + incluida.getPorcentage());
						relationships.add(newRelation);
					} catch (CloneNotSupportedException e) {
						e.printStackTrace();
					}
				}
				volverARecorrer = true;
			}
		}
		
		if(volverARecorrer) unirRelationshipsPorPorcentaje(relationships);
	}
}

/**
 * La comparacion es cierta cuando el objeto pasado por parametro tiene como fecha de inicio el dia inmediatamente posterior a la fecha de fin del objeto base.
 * El objeto base es el objeto pasado por el contructor.
 * @author ncortinez
 *
 */
class CorrelativaPorDerecha implements Predicate{

	private RequirementAssignmentRelationship relationship;
	
	public CorrelativaPorDerecha(RequirementAssignmentRelationship relationship) {
		this.relationship = relationship;
	}

	@Override
	public boolean evaluate(Object arg) {
		RequirementAssignmentRelationship relation =  (RequirementAssignmentRelationship)arg;

		// Es similiar si corresponde al mismo empleado y rol.
		boolean esSimilar = ((relation.getPorcentage() ==  this.relationship.getPorcentage()) 
							&& (relation.getEmployee().equals(this.relationship.getEmployee()))
									&& (relation.getRole().getDescription().equals(this.relationship.getRole().getDescription())));
		if(!esSimilar) return false;
		
		// Es correlativa por derecha si su fecha de inicio es el dia siguiente a la fecha de fin del objeto base.
		Calendar diaInicioCorrelativa = (Calendar) this.relationship.getEndDate().clone();
		diaInicioCorrelativa.add(Calendar.DAY_OF_YEAR, 1);
		boolean esCorrelativa = relation.getStartDate().compareTo(diaInicioCorrelativa) == 0;
		if(!esCorrelativa) return false;
		
		return esSimilar && esCorrelativa;
	}
}

/**
 * La comparacion es cierta cuando el objeto pasado por parametro tiene como fecha de fin el dia inmediatamente anterior a la fecha de inicio del objeto base.
 * El objeto base es el objeto pasado por el contructor.
 * @author ncortinez
 *
 */
class CorrelativaPorIzquierda implements Predicate{

	private RequirementAssignmentRelationship relationship;
	
	public CorrelativaPorIzquierda(RequirementAssignmentRelationship relationship) {
		this.relationship = relationship;
	}

	@Override
	public boolean evaluate(Object arg) {
		RequirementAssignmentRelationship relation =  (RequirementAssignmentRelationship)arg;
		
		// Es similiar si corresponde al mismo empleado y rol.
		boolean esSimilar = ((relation.getPorcentage() ==  this.relationship.getPorcentage()) 
							&& (relation.getEmployee().equals(this.relationship.getEmployee()))
									&& (relation.getRole().getDescription().equals(this.relationship.getRole().getDescription())));
		if(!esSimilar) return false;

		// Es correlativa por izquierda si su fecha de fin es el dia anterior a la fecha de inicio del objeto base.
		Calendar diaFinCorrelativa = (Calendar) this.relationship.getStartDate().clone();
		diaFinCorrelativa.add(Calendar.DAY_OF_YEAR, -1);
		boolean esCorrelativa = relation.getEndDate().compareTo(diaFinCorrelativa) == 0;
		if(!esCorrelativa) return false;
		
		return esSimilar && esCorrelativa;
	}
}

/**
 * La comparacion es cierta cuando el intervalo de fechas [startDate,endDate] del objeto pasado por parametro esta incluido en el rango de fechas del objeto base.
 * El objeto base es el objeto pasado por el contructor.
 * @author ncortinez
 *
 */
class RelationshipIncluidaEnOtra implements Predicate {

	private RequirementAssignmentRelationship baseRelationship;
	
	public RelationshipIncluidaEnOtra(
			RequirementAssignmentRelationship relationship) {
		this.baseRelationship = relationship;
	}

	@Override
	public boolean evaluate(Object arg) {
		RequirementAssignmentRelationship incluida = (RequirementAssignmentRelationship)arg;
		
		if(!incluida.getRole().getDescription().equals(this.baseRelationship.getRole().getDescription())) return false;
		if(!incluida.getEmployee().equals(this.baseRelationship.getEmployee())) return false;
		
		boolean startDateIncluido = (incluida.getStartDate().compareTo(this.baseRelationship.getStartDate()) >= 0) && (incluida.getStartDate().compareTo(this.baseRelationship.getEndDate()) <= 0);
		boolean endDateIncluido = (incluida.getEndDate().compareTo(this.baseRelationship.getEndDate()) <= 0) && (incluida.getEndDate().compareTo(this.baseRelationship.getStartDate()) >= 0);
		
		return (startDateIncluido || endDateIncluido);
	}
}

